/* 
 * Copyright (c) 2009-2012, Newcastle University, UK.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE. 
 * KL 29-03-2011 
 *
 * Uses the internal RTCC of the PIC24 
 */
 
// Includes
#include <Rtcc.h>
#include "HardwareProfile.h"
#include "myRTC.h"

// Globals
rtccTimeDate TheTime;
unsigned char maxDaysPerMonth[12+1] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
volatile unsigned int sampleTick;

// RTC init
void InitRTC(void)
{
	CLOCK_SOSCEN();
	RtccInitClock(); 
	RtccWrOn();
	mRtccOn();

	// Chime every 1 second to RTC int vector
	RtccWriteAlrmTimeDate(&TheTime);
	RtccSetChimeEnable(TRUE,TRUE);
	RtccSetAlarmRpt(RTCC_RPT_SEC, TRUE);
	RtccSetAlarmRptCount(1, TRUE); // Repeat once 
	mRtccSetAlrmPtr(0b0000); //Interrupt will fire <0.5s and chime will continue at rate
	mRtccAlrmEnable();
	mRtccSetIntPriority(4);	

	mRtccWrOff();

	// Check the time is a valid date
	UpdateTime();
	if	((FromBcd(TheTime.f.year)>99)||
		(FromBcd(TheTime.f.mday)>31)||
		(FromBcd(TheTime.f.mon)>12)||
		(FromBcd(TheTime.f.hour)>23)||
		(FromBcd(TheTime.f.min)>59)||
		(FromBcd(TheTime.f.sec)>59))
			{ClearRTC();}

	// Timer 1 setup - for millisecond values
	PR1 = ((unsigned long)((32768L)/T1_ISR_RATE)-1);		// Period
	sampleTick = 0; // Incremented in the T1 ISR

	T1CONbits.TSYNC= 1;	// Sync - for read/write alignment issue
	T1CONbits.TCS = 1;	// Secondary osc timer input
	T1CONbits.TCKPS = 0;// No prescale
	T1CONbits.TGATE = 0;// Ignored bit (Not Gated)
	T1CONbits.TSIDL = 1;// Timer on in idle mode
	T1CONbits.TON = 1;	// Timer on
	
	// Interrupt off, period is our ms sample clock
	IFS0bits.T1IF = 0;
	IEC0bits.T1IE = 0;
}

void ClearRTC(void)
{
	TheTime.f.mday = 0x01;
	TheTime.f.mon = 0x01;
	TheTime.f.year = 0x00;
	TheTime.f.hour = 0x00;
	TheTime.f.min = 0x00;
	TheTime.f.sec = 0x00;
	RtccInitClock(); 
	RtccWrOn();
	mRtccOn();
	RtccWriteTimeDate(&TheTime,1);
	mRtccWrOff();
}
void SetTime(void)
{
	RtccWrOn();
	mRtccOn();
	RtccWriteTimeDate(&TheTime,1);
	mRtccWrOff();
}
void UpdateTime(void)
{
	RtccWrOn();
	mRtccWaitSync(); // dont read until safe to do so.
	RtccReadTimeDate(&TheTime);
	mRtccWrOff();
}

void SetupAlarm (void)
{
	RtccWrOn();
	RtccWriteAlrmTimeDate(&TheTime);
	RtccSetChimeEnable(FALSE,TRUE);
	//RtccSetAlarmRpt(RTCC_RPT_HALF_SEC, TRUE);
	RtccSetAlarmRpt(RTCC_RPT_SEC, TRUE);
	RtccSetAlarmRptCount(1, TRUE); // Repeat once 
	mRtccSetAlrmPtr(0b0110); // How many fields need to match for alarm to occur - HH:MM:SS, p261 datasheet
	mRtccAlrmEnable();
	mRtccSetIntPriority(4);	
	mRtccWrOff();
}

void SetupChimeOn (void) // Note: Chime enable just lets the alarm keep repeating indefinitely, still need one alarm to start it.
{
	RtccWrOn();
	RtccWriteAlrmTimeDate(&TheTime);
	RtccSetChimeEnable(TRUE,TRUE);
	RtccSetAlarmRpt(RTCC_RPT_SEC, TRUE);
	RtccSetAlarmRptCount(1, TRUE); // Repeat once 
	mRtccSetAlrmPtr(0b0000); //Interrupt will fire <0.5s and chime will continue at rate
	mRtccAlrmEnable();
	mRtccSetIntPriority(4);	
	mRtccWrOff();
}

void SetupChimeOff (void) 
{
	RtccWrOn();
	RtccSetChimeEnable(FALSE,TRUE);
	mRtccWrOff();
}

void TurnOnRTCInt(void)
{
	IFS3bits.RTCIF	= 0;
	IEC3bits.RTCIE 	= 1;
}
void TurnOffRTCInt(void)
{
	IFS3bits.RTCIF	= 0;
	IEC3bits.RTCIE 	= 0;
}

// Convert a date/time number from a string ("YY/MM/DD,HH:MM:SS+00" -- AT+CCLK compatible for default format)
rtccTimeDate* DateTimeFromString(const char *value)
{
    int index;
    unsigned int v;
    unsigned char year, month, day, hours, minutes, seconds;
    const char *c;

	if (value[0] == '\0') { return NULL; }
	if (value[0] == '0' && value[1] == '\0') { return NULL; }
	if (value[0] == '-') { return NULL; }
    index = 0;
    v = 0xffff;
    c = value;
    for (;;)
    {
        if (*c >= '0' && *c <= '9') // Part of value
        {
            if (v == 0xffff) { v = 0; }
            v = (v * 10) + (*c - '0');
        }
        else
        {
            if (v != 0xffff)  // End of value
            {
                if      (index == 0) { year = (v >= 2000) ? (v - 2000) : v; }
                else if (index == 1) { month = v; }
                else if (index == 2) { day = v; }
                else if (index == 3) { hours = v; }
                else if (index == 4) { minutes = v; }
                else if (index == 5) { seconds = v; }
                else { break; }
                index++;
                v = 0xffff;
                if (index >= 6) { break; }
            }
            if (*c == '\0') { break; }
        }
        c++;
    }

    // Check if parsed six elements and check validity of members
    if (    index == 6 &&
            (month >= 1 && month <= 12) &&
            (day >= 1 && day <= maxDaysPerMonth[month]) &&
            (/*hours >= 0 &&*/ hours <= 23) &&
            (/*minutes >= 0 &&*/ hours <= 59) &&
            (/*seconds >= 0 &&*/ seconds <= 59)
        )
    {
		if (month == 2 && day == 29 && (year & 3) != 0) { month = 3; day = 1; }	// Turn 29-Feb in non-leap years into 1-Mar
		TheTime.f.mday = 	ToBcd(day);
		TheTime.f.mon = 	ToBcd(month);
		TheTime.f.year = 	ToBcd(year);
		TheTime.f.hour = 	ToBcd(hours);
		TheTime.f.min = 	ToBcd(minutes);
		TheTime.f.sec = 	ToBcd(seconds);
        return &TheTime;
    }
    else
    {
        return NULL;
    }   
}


// Convert a date/time number to a string ("yyYY/MM/DD,HH:MM:SS+00" -- AT+CCLK compatible for default format)
const char *DateTimeToString(rtccTimeDate* value)
{
    // "yyYY/MM/DD HH:MM:SS+00"
    static char dateTimeString[23];
    char *c;
    unsigned int v;

    c = dateTimeString;

	if (value == NULL)
	{
	    *c++ = '0'; *c++ = '\0';				// "0"
	}
	else
	{
	    v = 2000 + FromBcd(value->f.year); 	
		*c++ = '2'; *c++ = '0'; *c++ = '0' + (value->f.year>>4); *c++ = '0' + (value->f.year&0xf);
	    *c++ = '/';
		*c++ = '0' + (value->f.mon>>4); *c++ = '0' + (value->f.mon&0xf);
	    *c++ = '/';
		*c++ = '0' + (value->f.mday>>4); *c++ = '0' + (value->f.mday&0xf);
	    *c++ = ' ';
		*c++ = '0' + (value->f.hour>>4); *c++ = '0' + (value->f.hour&0xf);
	    *c++ = ':';
		*c++ = '0' + (value->f.min>>4); *c++ = '0' + (value->f.min&0xf);
	    *c++ = ':';
		*c++ = '0' + (value->f.sec>>4); *c++ = '0' + (value->f.sec&0xf);

	    *c++ = '\0';
	}
    return dateTimeString;
}

unsigned long PackTime (void) // Update time stamp first
{
	// 'DateTime' bit pattern:  YYYYYYMM MMDDDDDh hhhhmmmm mmssssss
	unsigned char year = FromBcd(TheTime.f.year);
	unsigned char mon = FromBcd(TheTime.f.mon);
	unsigned char mday = FromBcd(TheTime.f.mday);
	unsigned char hour = FromBcd(TheTime.f.hour);
	unsigned char min = FromBcd(TheTime.f.min);
	unsigned char sec = FromBcd(TheTime.f.sec);

	unsigned long retval = sec;
	retval |= (unsigned long) year<<26;
	retval |= (unsigned long) mon<<22;
	retval |= (unsigned long) mday<<17;
	retval |= (unsigned long) hour<<12;
	retval |= (unsigned long) min<<6;

	return retval;
}

